Como usar modulos shift registers (74HC595) para aumentar el numero de puertos de salida.
Datasheet 74HC595Pines 74HC595:
El modulo 74HC595 nos permite usar una señal serie para convertirla en salidas digitales.
Usamos principalmente 3 pines del 74HC595 para controlarlo.
Por lo tanto el funcionamiento es, usamos Data para decidir si enviamos 0 o 1, despues con Clock vamos enviando los bits que queremos (cambiando Data si fuese necesario), y por ultimo Latch para mostrar el resultado, en modulos como el 74HC164 no hay latch y los resultados se muestran inmediatamente según se van enviando con clock.
El modulo 74HC595 no se usa de forma manual, pero está bien como ejemplo practico para aprender como funciona.
Para usar el modulo manualmente el circuito es el siguiente:
Las conexiones que tenemos con el modulo son las siguientes:
Conexiones a positivo:
Conexiones a tierra:
NOTA: todos los pulsadores/botones que tenemos conectados tienen que tener una resistencia pull up/down (según el pin funcione con positivo o negativo), para que funcionen correctamente, ya que un botón no pulsado básicamente es un cable al aire que puede actuar como una antena y dar una entrada indefinida.
Al usar un modulo 74HC595 con un microcontrolador nos permite aumentar el numero de salidas del microcontrolador, para manejar el modulo solo necesitamos 3 pines del microcontrolador y cada modulo 74HC595 se puede conectar en cascada a otro modulo 74HC595 a traves del pin Q7' al pin DS del siguiente modulo.
En el siguiente ejemplo se muestra como con solo 3 pines del microcontrolador podemos controlar 3 led matrix que requieren de 32 pines para hacerlas funcionar (24 pines para las columnas y 8 para las filas).
Los pines del microcontrolador son los siguientes:
Usamos 4 modulos 74HC595 conectados en cascada, 3 para controlar las columnas de las matrices y 1 para controlar las filas:
Los shift registers en este circuito funcionan de la siguiente manera: envíamos 4 bytes a traves del SPI, 1 byte por cada modulo, el primer byte será el que corresponda al último modulo (el que controla las filas), ya que los bits van pasando de modulo en modulo según se envían, por lo tanto el primer byte se quedará en el último modulo y el último byte se quedará en el primer módulo.
El código para hacer funcionar las 3 matrices con desplazamiento de caracteres es el siguiente:
NOTA: si queremos adaptar el código a nuestro circuito solo tenemos que cambiar NUMBER_OF_DISPLAYS para indicar el numero de matrices que tenemos.
NOTA2: el archivo de cabecera characters.h está en la página de Matrix led
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "characters.h"
#define LATCH_PIN PB2 // ST_CP
#define DISPLAY_COLUMNS 8
#define DISPLAY_ROWS 8
#define CHAR_COLUMNS 8
#define NUMBER_OF_DISPLAYS 3
#define SLIDE_SPEED_DELAY 60
#define SPACE 0
#define A 1
#define B 2
#define C 3
volatile uint8_t display[NUMBER_OF_DISPLAYS][DISPLAY_ROWS];
volatile uint8_t count = 0;
void SPI_init(void) {
// Configurar MOSI, SCK y LATCH como salida
DDRB |= (1 << PB3) | (1 << PB5) | (1 << LATCH_PIN);
// Configurar SPI como Master
SPCR = (1 << SPE) | (1 << MSTR);
}
void SPI_send(uint8_t data) {
SPDR = data; // Cargar dato en el registro de SPI
while (!(SPSR & (1 << SPIF))); // Esperar que termine la transmisión
}
void latch() {
// Pulso en LATCH para actualizar salidas
PORTB |= (1 << LATCH_PIN);
//_delay_us(1); // Opcional? descomentar en caso de que el latch no funcione
PORTB &= ~(1 << LATCH_PIN);
}
ISR(TIMER0_COMPA_vect) {
SPI_send(~(1 << count));
for(int8_t i = NUMBER_OF_DISPLAYS-1; i >= 0; i--)
SPI_send(display[i][count]);
latch(),
count++;
if(count >= DISPLAY_COLUMNS)
count=0;
}
void init_timer0(){
TCCR0A |= (1 << WGM01);
TCCR0B |= (1 << CS02); // Prescaler de 256
TIMSK0 |= (1 << OCIE0A);
OCR0A = 2; // Interrupción cada 512 microsegundos
sei();
}
void print_slide_characters(uint8_t *char_list, uint8_t char_list_size){
uint8_t current_char_column = 0;
uint8_t current_char = 0;
while(current_char < char_list_size){
for(uint8_t i = 0; i < DISPLAY_ROWS; i++){
for(uint8_t j = 0; j < NUMBER_OF_DISPLAYS - 1; j++){
display[j][i] = (display[j][i] >> 1); // slide pixels
display[j][i] |= (display[j+1][i] & 0b00000001) << (DISPLAY_COLUMNS - 1);
}
display[NUMBER_OF_DISPLAYS-1][i] = (display[NUMBER_OF_DISPLAYS-1][i] >> 1); // slide pixels
uint8_t character_id = char_list[current_char];
display[NUMBER_OF_DISPLAYS-1][i] |= ((characters[character_id][i] >> current_char_column) & 0b00000001) << (CHAR_COLUMNS - 1); // Feed last column with new char (<<7 to put LSB as MSB)
}
current_char_column++;
if(current_char_column == CHAR_COLUMNS){
current_char_column = 0;
current_char++;
}
_delay_ms(SLIDE_SPEED_DELAY);
}
}
int main(void) {
SPI_init();
init_timer0();
uint8_t char_list[] = {A,B,C, SPACE, SPACE, SPACE};
uint8_t char_list_size = sizeof(char_list);
while (1) {
print_slide_characters(char_list, char_list_size);
}
}
En este ejemplo en concreto tenemos que tener en cuenta que estamos manejando muchas salidas (32 salidas) y a mayores estamos haciendo multiplexación de las filas para conseguir un efecto POV (Persistence Of Vision) y que así podamos iluminar todos los leds que necesitamos para crear los caracteres (En total estamos manejando 192 leds), por lo tanto podemos llegar al punto de saturar el microcontrolador con demasiadas interrupciones y hacer que el desplazamiento lateral no avance como nosotros queremos.
En este caso las interrupciones para iluminar cada fila se producen cada 512 microsegundos, y el prescaler del SPI está en /4 (por defecto), por lo tanto cada bit se envía cada 4 ciclos de reloj (4 microsegundos con la frecuencia a 1Mhz), por lo tanto para enviar toda la información de una fila se tardan, 128 microsegundos (32 microsegundos por byte * 4 bytes, uno por cada modulo 74HC595), en este escenario con estos datos funciona bien, pero si cambiamos por ejemplo el tiempo de actualización de cada fila a 256 microsegundos ya veremos que el desplazamiento es bastante mas lento, esto se produce porque el SPI no tiene tiempo a enviar todos los datos y apenas terminar de enviar los datos ya se produce una nueva interrupción para actualizar una nueva fila, haciendo que el bucle principal, que es el que genera el desplazamiento, apenas tenga tiempo para ser ejecutado.
AVR | shift register | 74HC595